/*
 *  cpu.c
 *
 *  Created on: 2010-5-12
 *      Author: liuyond
 */
#include "cpu.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern int fetch_and_decode(cpu_t *, instruction_t *);
extern int calculate_cf_flag(cpu_t *, int, int, int, int);
extern int calculate_of_flag(cpu_t *, int, int, int, int);
extern int calculate_sf_flag(cpu_t *, int, int, int, int);
extern int calculate_zf_flag(cpu_t *, int, int, int, int);
extern void cpu_show_all(cpu_t *);

/**
 * cpu_init - initialize cpu's all registers
 * @cpu:	the cpu to got initialized
 * @return:	will always success, return nothing
 */
int cpu_init(cpu_t *cpu)
{
	memset(cpu, 0, sizeof(cpu_t));
	// allocate 1M memory
	cpu->ram = malloc(1 << 20);
	if (cpu->ram == NULL)
	{
		fprintf(stderr, "cpu_init failed on allocate memory!\n");
		return -1;
	}
	memset(cpu->ram, 0, (1<<20));

	return 0;
}

/**
 * cpu_get_reg(cpu_t *cpu, int index)
 */
int cpu_get_reg(cpu_t *cpu, int index)
{
	return cpu->regs[index];
}
/**
 * cpu_set_reg(cpu_t *cpu, int index, int value)
 */
void cpu_set_reg(cpu_t *cpu, int index, int value)
{
	cpu->regs[index] = value;
}
/**
 * cpu_get_regb(cpu_t *cpu, int index)
 */
int cpu_get_regb(cpu_t *cpu, int index)
{
	int ret;

	if (index >= 4)
	{
		index -= 4;
		ret = cpu->regs[index] & 0xFF00;
		ret = ret >> 8;
	}
	else
	{
		ret = cpu->regs[index] & 0xFF;
	}
	return ret;
}
/**
 * cpu_set_regb(cpu_t *cpu, int index, int value)
 */
void cpu_set_regb(cpu_t *cpu, int index, int value)
{
	int tmp;

	if (index >= 4)
	{
		index -= 4;
		value &= 0xFF;
		value = value << 8;
		tmp = cpu->regs[index];
		tmp = tmp & 0xFF;
		tmp = tmp | value;
		cpu->regs[index] = tmp;
	}
	else
	{
		tmp = cpu->regs[index];
		tmp = tmp & 0xFF00;
		tmp = tmp | (value & 0xFF);
		cpu->regs[index] = tmp;
	}
}
/**
 * cpu_get_seg(cpu_t *cpu, int index)
 */
int cpu_get_seg(cpu_t *cpu, int index)
{
	return cpu->segs[index];
}
/**
 * cpu_set_seg(cpu_t *cpu, int index, int value)
 */
void cpu_set_seg(cpu_t *cpu, int index, int value)
{
	cpu->segs[index] = value;
}

/**
 * cpu_get_flag(cpu_t *cpu, int index)
 */
int cpu_get_flag(cpu_t *cpu, int index)
{
	return ((cpu->flags & (1 << index)) ? 1 : 0);
}

/**
 * cpu_set_flag(cpu_t *cpu, int index, int value)
 */
int cpu_set_flag(cpu_t *cpu, int index, int value)
{
	int temp;

	if (index < 0 || index > 15)
		return -1;

	temp = (1 << index);
	if (value != 0)
	{
		cpu->flags |= temp;
	}
	else
	{
		cpu->flags &= ~temp;
	}
	return 0;
}

/**
 * cpu_set_flags(cpu_t *cpu, int inst, int src, int dst, int ret)
 */
void cpu_set_flags(cpu_t *cpu, int inst, int src, int dst, int ret)
{
	int  cf, of, sf, zf;

	// calculate flags
	cf = calculate_cf_flag(cpu, inst, src, dst, ret);
	of = calculate_of_flag(cpu, inst, src, dst, ret);
	sf = calculate_sf_flag(cpu, inst, src, dst, ret);
	zf = calculate_zf_flag(cpu, inst, src, dst, ret);
	// set flags
	cpu_set_flag(cpu, CF, cf);
	cpu_set_flag(cpu, OF, of);
	cpu_set_flag(cpu, SF, sf);
	cpu_set_flag(cpu, ZF, zf);
}

/**
 * cpu_get_mem_byte(cpu_t *cpu, int seg, int offset)
 */
int cpu_get_mem_byte(cpu_t *cpu, int seg, int offset)
{
	int segment = cpu->segs[seg];
	int physical = (segment << 4) + offset;
	return fetch_byte(&cpu->ram[physical]);
}
/**
 * cpu_get_mem_word(cpu_t *cpu, int seg, int offset)
 */
int cpu_get_mem_word(cpu_t *cpu, int seg, int offset)
{
	int segment = cpu->segs[seg];
	int physical = (segment << 4) + offset;
	return fetch_word(&cpu->ram[physical]);
}
/**
 * cpu_get_mem_dword(cpu_t *cpu, int seg, int offset)
 */
int cpu_get_mem_dword(cpu_t *cpu, int seg, int offset)
{
	int segment = cpu->segs[seg];
	int physical = (segment << 4) + offset;
	return fetch_dword(&cpu->ram[physical]);
}
/**
 * cpu_set_mem_byte(cpu_t *cpu, int seg, int offset, int value)
 */
void cpu_set_mem_byte(cpu_t *cpu, int seg, int offset, int value)
{
	int segment = cpu->segs[seg];
	int physical = (segment << 4) + offset;
	*BYTEP(&cpu->ram[physical]) = value;
}
/**
 * cpu_set_mem_word(cpu_t *cpu, int seg, int offset, int value)
 */
void cpu_set_mem_word(cpu_t *cpu, int seg, int offset, int value)
{
	int segment = cpu->segs[seg];
	int physical = (segment << 4) + offset;
	*WORDP(&cpu->ram[physical]) = value;
}
/**
 * cpu_set_mem_dword(cpu_t *cpu, int seg, int offset, int value)
 */
void cpu_set_mem_dword(cpu_t *cpu, int seg, int offset, int value)
{
	int segment = cpu->segs[seg];
	int physical = (segment << 4) + offset;
	*DWORDP(&cpu->ram[physical]) = value;
}
/**
 * cpu_push_mem_word(cpu_t *cpu, int value)
 */
void cpu_push_mem_word(cpu_t *cpu, int value)
{
	int off = cpu_get_reg(cpu, SP);
	cpu_set_reg(cpu, SP, off-sizeof(word));
	cpu_set_mem_word(cpu, cpu_get_seg(cpu, SS),
			cpu_get_reg(cpu, SP), value);
}
/**
 * cpu_push_mem_dword(cpu_t *cpu, int value)
 */
void cpu_push_mem_dword(cpu_t *cpu, int value)
{
	int off = cpu_get_reg(cpu, SP);
	cpu_set_reg(cpu, SP, off-sizeof(dword));
	cpu_set_mem_dword(cpu, cpu_get_seg(cpu, SS),
			cpu_get_reg(cpu, SP), value);
}
/**
 * cpu_pop_mem_word(cpu_t *cpu)
 */
int cpu_pop_mem_word(cpu_t *cpu)
{
	int seg = cpu_get_seg(cpu, SS);
	int off = cpu_get_reg(cpu, SP);
	int phy = ((seg << 4) + (off));
	int ret = fetch_word(&cpu->ram[phy]);
	cpu_set_reg(cpu, SP, off+sizeof(word));
	return ret;
}
/**
 * cpu_pop_mem_dword(cpu_t *cpu)
 */
int cpu_pop_mem_dword(cpu_t *cpu)
{
	int seg = cpu_get_seg(cpu, SS);
	int off = cpu_get_reg(cpu, SP);
	int phy = ((seg << 4) + (off));
	int ret = fetch_dword(&cpu->ram[phy]);
	cpu_set_reg(cpu, SP, off+sizeof(dword));
	return ret;
}

/**
 * cpu_halt - return a boolean value indicate if the cpu has halted
 * @cpu:    a instance of cpu_t which stores cpu's status
 * @return:	return true if the cpu has halted, otherwise return false
 */
int cpu_halt(cpu_t *cpu)
{
	return (cpu->status & CPU_HALT);
}

/**
 * handle_asyn_event - handle all kinds of asynchronous event
 * @return:	return nothing
 */
void handle_asyn_event()
{
	// don't support yet!
}

/**
 * cpu_loop - the main routine of simulate
 */
void cpu_loop(cpu_t *cpu)
{
	unsigned short cx, repeat;
	instruction_t instruction;

	// don't forget to set stack
	while (!cpu_halt(cpu))
	{
		memset(&instruction, 0, sizeof(instruction));
		// 1. fetch and decode
		fetch_and_decode(cpu, &instruction);
		// 2. dispatch and execute
		if (!instruction.repeatable)
		{
			instruction.execute(cpu, &instruction);
			handle_asyn_event();
			cpu_show_all(cpu);
		}
		else
		{
			repeat = 1;
			while (repeat)
			{
				if (cx = cpu_get_reg(cpu, CX))
				{
					instruction.execute(cpu, &instruction);
					handle_asyn_event();
					cpu_show_all(cpu);
				}
				cpu_set_reg(cpu, CX, cx-1);
				if (!cpu_get_reg(cpu, CX))
				{
					repeat = 0;
				}
				else if (instruction.repeatable == 2
					   && cpu_get_flag(cpu, ZF) != 0) // REPNE/REPNZ
				{
					repeat = 0;
				}
				//else if (instruction.repeatable == 3
				//	   && cpu_get_flag(cpu, ZF) == 0) // REPZ/REPE
				//{
				//	repeat = 0;
				//}
			}
		}
	}
	fprintf(stdout, "cpu halted!\n");
}
